﻿#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import ftdi
import math

class GraphicLCD:
	### ユーザーメソッド ###
	# <summary>
	# ピンアサインを設定します。
	# </summary>
	# <param name="latch">ラッチ</param>
	# <param name="a0">データ・コマンド切り替え</param>
	# <param name="enable">イネーブル</param>
	# <param name="reset">リセット</param>
	# <param name="d0">データピン0</param>
	# <param name="d1">データピン1</param>
	# <param name="d2">データピン2</param>
	# <param name="d3">データピン3</param>
	def __init__(self, latch, a0, enable, reset, d0, d1, d2, d3):
		## 定数宣言 ##
		# FTDIデバイス #
#		self.FTDI_BAUDRATE = 230400  # bps
		self.FTDI_BAUDRATE = 350000  # bps
		self.FTDI_VENDOR = 0x0403
		self.FTDI_PRODUCT = 0x6001  # FT232RL
		#self.FTDI_PRODUCT = 0x6010  # FT2232D
		self.FTDI_INTERFACE = ftdi.INTERFACE_A

		# LQ020B8UB02 コマンド #
		self.CASET = 0x15  # Column Address  [start column address(lower)] [start column address(upper)] [end column address(lower)] [end column address(upper)]
		self.PASET = 0x75  # Page Address [start page address] [end page address ]
		self.PFSET = 0xC8  # Pixcel Format [sub pixel format]
		self.RAMWR = 0x5C  # Write RAM [color code for the 1st pixel] [Color code for the 2nd pixel] [Color code for the 3rd pixel]
		self.RAMRD = 0x5D  # Read RAM [Read dummy data] [Color code for the 1st pixel] [] [Color code for the 2nd pixel] [] [Color code for the 3rd pixel] [] [similarly]
		self.DISNOR = 0xA6  # Display Normal
		self.DISINV = 0xA7  # Display inverted
		self.SLPOUT = 0x94  # Sleep Disable
		self.SLPIN = 0x95  # Sleep Enable
		self.LPIN = 0xA8  # Low Power Enable [start page address to scan from] [end page address to scan up to] [the number of colors]
		self.LPOUT = 0xA9  # Low Power Disable
		self.DISOFF = 0xAE  # Display Off
		self.DISON = 0xAF  # Display On
		self.VOLCTL = 0xC6  # Volume Control [parameter]
		self.NOP = 0x25  # Non Operation
		self.READID1 = 0xDA  # Read ID1 [read dummy data] [read ID1 data]
		self.READID2 = 0xDB  # Read ID2 [read dummy data] [read ID2 data]

		# 表示サイズ #
		self._GLCD_Width = 132
		self._GLCD_Height = 162

		## 変数に記憶 ##
		self._latch_pin = 1 << latch
		self._a0_pin = 1 << a0
		self._enable_pin = 1 << enable
		self._reset_pin = 1 << reset

		self._data_pins = ( 1 << d0, 1 << d1, 1 << d2, 1 << d3 )

		## コマンドリスト ##
		# クリア #
		self._commandList = []


	# <summary>
	# グラフィック液晶の幅（ピクセル単位）を取得します。
	# </summary>
	def Width(self):
		return self._GLCD_Width


	# <summary>
	# グラフィック液晶の高さ（ピクセル単位）を取得します。
	# </summary>
	def Height(self):
		return self._GLCD_Height


	# <summary>
	# デバイスを初期化します。
	# </summary>
	def Begin(self, width, height):
		## 変数に記憶 ##
		self._GLCD_Width = width
		self._GLCD_Height = height

		## FTDIデバイスを初期化 ##
		# インスタンス作成 #
		self._FTDI_Device = ftdi.ftdi_new()

		# インターフェース設定 #
		self._checkStatus(ftdi.ftdi_set_interface(self._FTDI_Device, self.FTDI_INTERFACE))

		# 接続 #
		self._checkStatus(ftdi.ftdi_usb_open(self._FTDI_Device, self.FTDI_VENDOR, self.FTDI_PRODUCT))

		# IOモード設定 #
		port = (self._latch_pin |
			self._a0_pin |
			self._enable_pin |
			self._reset_pin |
			self._data_pins[0] |
			self._data_pins[1] |
			self._data_pins[2] |
			self._data_pins[3])
		self._checkStatus(ftdi.ftdi_enable_bitbang(self._FTDI_Device, port))

		# ボーレート設定 #
		self._checkStatus(ftdi.ftdi_set_baudrate(self._FTDI_Device, self.FTDI_BAUDRATE))

		## グラフィックLCDの初期化 ##
		# ポート初期化 #
		self._commandList.append(0x00)
		self._commandList.append(self._latch_pin)
		self._commandList.append(0x00)

		# 初期化コマンド #
		self._commandList.append(self._enable_pin)
		self._commandDelay(100)

		self._commandList.append(self._enable_pin | self._reset_pin)
		self._commandDelay(100)

		self.NoSleep()
		self._commandDelay(100)

		self.Clear(0xFFFF)

		self.Display()

		# 処理を実行 #
		self.Sync()


	# <summary>
	# デバイスとの接続を切断します。
	# </summary>
	def End(self):
		try:
			# コマンドリスト クリア #
			self._commandList = []
	
			# スリープモードに移行 #
			self.NoDisplay()
			self.Sleep()
	
			# 処理を実行 #
			self.Sync()
	
			# FTDIデバイスとの接続を切断 #
			self._checkStatus(ftdi.ftdi_usb_close(self._FTDI_Device))
			ftdi.ftdi_deinit(self._FTDI_Device)

		except AttributeError:
			pass


	# <summary>
	# コマンドの文字列を返します。
	# </summary>
	def ToString(self):
		return "".join(map(chr, self._commandList))


	# <summary>
	# コマンドのリストを返します。
	# </summary>
	def ToList(self):
		return self._commandList


	# <summary>
	# コマンドのタプルを返します。
	# </summary>
	def ToTuple(self):
		return tuple(self._commandList)


	# <summary>
	# バッファリングされたデータを転送します。
	# </summary>
	def Sync(self):
		# データを転送 #
		if len(self._commandList) == 0:
			return

		binLine = "".join(map(chr, self._commandList))
		ftdi.ftdi_write_data(self._FTDI_Device, binLine, len(binLine))
		self._commandList = []


	# <summary>
	# 制御コードを転送します。
	# </summary>
	def Write(self, data):
		ftdi.ftdi_write_data(self._FTDI_Device, data, len(data))


	# <summary>
	# バッファをクリアします。
	# </summary>
	def Reset(self):
		self._commandList = []


	# <summary>
	# 表示をクリアします。
	# </summary>
	# <param name="color">塗りつぶし色</param>
	# <param name="x">四角形の左上隅のx座標</param>
	# <param name="y">四角形の左上隅のy座標</param>
	# <param name="width">四角形の幅</param>
	# <param name="width">四角形の高さ</param>
	# <param name="sync">即時反映</param>
	def Clear(self, color, x = 0, y = 0, width = 0, height = 0, sync = False):
		# 表示幅高さ設定 #
		width = self._GLCD_Width if width == 0 else width
		height = self._GLCD_Height if height == 0 else height

		# 表示エリアを設定 #
		self._drawArea(x, y, width, height)

		# RAMに書き込み #
		self._cmd8bits(self.RAMWR)
		for i in range(width * height):
			self._data8bits(color >> 8)
			self._data8bits(color)

		# コマンドを送信 #
		if sync:
			self.Sync()


	# <summary>
	# ビットマップを描画します。
	# </summary>
	# <param name="array">色の数値配列</param>
	# <param name="x">ビットマップの左上隅のx座標</param>
	# <param name="y">ビットマップの左上隅のy座標</param>
	# <param name="width">ビットマップの幅</param>
	# <param name="width">ビットマップの高さ</param>
	# <param name="sync">即時反映</param>
	def Draw(self, data, x = 0, y = 0, width = 0, height = 0, sync = False):
		# 表示エリアを設定 #
		self._drawArea(x, y, width, height)

		# RAMに書き込み #
		self._cmd8bits(self.RAMWR)

		for i in range(0, len(data), 3):
			r = ord(data[i + 0])
			g = ord(data[i + 1])
			b = ord(data[i + 2])

			color = ((r >> 3) << 11) | ((g >> 3) << 6) | (b >> 3)

			self._data8bits(color >> 8)
			self._data8bits(color)

		# コマンドを送信 #
		if sync:
			self.Sync()


	# <summary>
	# ディスプレイを有効化します。
	# </summary>
	# <param name="sync">即時反映</param>
	def Display(self, sync = False):
		# コマンドを追加 #
		self._cmd8bits(self.DISON)

		# コマンドを送信 #
		if sync:
			self.Sync()


	# <summary>
	# ディスプレイを無効化します。
	# </summary>
	# <param name="sync">即時反映</param>
	def NoDisplay(self, sync = False):
		# コマンドを追加 #
		self._cmd8bits(self.DISOFF)

		# コマンドを送信 #
		if sync:
			self.Sync()


	# <summary>
	# スリープモードに移行します。
	# </summary>
	# <param name="sync">即時反映</param>
	def Sleep(self, sync = False):
		# コマンドを追加 #
		self._cmd8bits(self.SLPIN)

		# コマンドを送信 #
		if sync:
			self.Sync()


	# <summary>
	# スリープモードから回復します。
	# </summary>
	# <param name="sync">即時反映</param>
	def NoSleep(self, sync = False):
		# コマンドを追加 #
		self._cmd8bits(self.SLPOUT)

		# コマンドを送信 #
		if sync:
			self.Sync()


	# <summary>
	# 通常モードで表示します。
	# </summary>
	# <param name="sync">即時反映</param>
	def DisplayNormal(self, sync = False):
		# コマンドを追加 #
		self._cmd8bits(DISNOR)

		# コマンドを送信 #
		if sync:
			self.Sync()


	# <summary>
	# 反転モードで表示します。
	# </summary>
	# <param name="sync">即時反映</param>
	def DisplayInverted(self, sync = False):
		# コマンドを追加 #
		self._cmd8bits(self.DISINV)

		# コマンドを送信 #
		if sync:
			self.Sync()


	### クラス内メソッド ###
	def _cmd8bits(self, value):
		self._commandAdd(value, False)


	def _data8bits(self, value):
		self._commandAdd(value, True)


	def _commandAdd(self, value, a0):
		# データ セット #
		dataUpper = self._data2pin((value >> 4) & 0x0F)
		dataLower = self._data2pin(value & 0x0F)

		# コマンド追加 #
		if a0:
			# データモード
			self._commandList.append(dataUpper | self._a0_pin | self._reset_pin | self._enable_pin)
			self._commandList.append(dataUpper | self._a0_pin | self._reset_pin | self._enable_pin | self._latch_pin)

			#self._commandList.append(dataLower | self._a0_pin | self._reset_pin | self._enable_pin)
			self._commandList.append(dataLower | self._a0_pin | self._reset_pin)
			#self._commandList.append(dataLower | self._a0_pin | self._reset_pin | self._enable_pin)
		else:
			# コマンドモード
			self._commandList.append(dataUpper | self._reset_pin | self._enable_pin)
			self._commandList.append(dataUpper | self._reset_pin | self._enable_pin | self._latch_pin)

			#self._commandList.append(dataLower | self._reset_pin | self._enable_pin)
			self._commandList.append(dataLower | self._reset_pin)
			#self._commandList.append(dataLower | self._reset_pin | self._enable_pin)


	def _commandDelay(self, microseconds):
		if len(self._commandList) == 0:
			return

		lastCommand = self._commandList[len(self._commandList) - 1]

		nopCount = int(microseconds * self.FTDI_BAUDRATE / math.pow(10, 5))  # 補正値
		#nopCount = int(microseconds * self.FTDI_BAUDRATE / math.pow(10, 6))  # 計算値

		for i in range(nopCount):
			self._commandList.append(lastCommand)


	def _data2pin(self, data):
		# データをピンに変換 #
		pinData = 0

		for i in range(len(self._data_pins)):
			if ((data >> i) & 0x01) == 1:
				pinData |= self._data_pins[i]

		return pinData


	def _drawArea(self, x, y, width, height):
		# コラム アドレス #
		self._cmd8bits(self.CASET)
		self._data8bits(x)  # 開始アドレス 下位
		self._data8bits(x >> 8)  # 開始アドレス 上位

		stopAddress = ((width * 2) - 1)
		self._data8bits(stopAddress)  # 終了アドレス 下位
		self._data8bits(stopAddress >> 8)  # 終了アドレス 上位

		# ページ アドレス #
		self._cmd8bits(self.PASET)
		self._data8bits(y)  # 開始アドレス
		self._data8bits(height - 1)  # 終了アドレス


	def _checkStatus(self, status):
#		print status
		if status == 0:
			return


#		if status == FTDI.FT_STATUS.FT_OK:
#			return
#
#		elif status == FTDI.FT_STATUS.FT_DEVICE_NOT_FOUND:
#			throw new USB_IOException("デバイスが見つかりません。")
#
#		elif status == FTDI.FT_STATUS.FT_DEVICE_NOT_OPENED:
#			throw new USB_IOException("デバイスと接続されていません。")
#
#		elif status == FTDI.FT_STATUS.FT_DEVICE_NOT_OPENED_FOR_ERASE:
#			throw new USB_IOException("削除のためにデバイスと接続されていません。")
#
#		elif status == FTDI.FT_STATUS.FT_DEVICE_NOT_OPENED_FOR_WRITE:
#			throw new USB_IOException("書き込むためにデバイスと接続されていません。")
#
#		elif status == FTDI.FT_STATUS.FT_EEPROM_ERASE_FAILED:
#			throw new USB_IOException("EEPROMの削除に失敗しました。")
#
#		elif status == FTDI.FT_STATUS.FT_EEPROM_NOT_PRESENT:
#			throw new USB_IOException("EEPROMが見つかりません。")
#
#		elif status == FTDI.FT_STATUS.FT_EEPROM_NOT_PROGRAMMED:
#			throw new USB_IOException("EEPROMがプログラムされていません。")
#
#		elif status == FTDI.FT_STATUS.FT_EEPROM_READ_FAILED:
#			throw new USB_IOException("EEPROMの読み込みに失敗しました。")
#
#		elif status == FTDI.FT_STATUS.FT_EEPROM_WRITE_FAILED:
#			throw new USB_IOException("EEPROMの書き込みに失敗しました。")
#
#		elif status == FTDI.FT_STATUS.FT_FAILED_TO_WRITE_DEVICE:
#			throw new USB_IOException("データ転送に失敗しました。")
#
#		elif status == FTDI.FT_STATUS.FT_INSUFFICIENT_RESOURCES:
#			throw new USB_IOException("リソースが不足しています。")
#
#		elif status == FTDI.FT_STATUS.FT_INVALID_ARGS:
#			throw new USB_IOException("引数が無効です。")
#
#		elif status == FTDI.FT_STATUS.FT_INVALID_BAUD_RATE:
#			throw new USB_IOException("ボーレートが無効です。")
#
#		elif status == FTDI.FT_STATUS.FT_INVALID_HANDLE:
#			throw new USB_IOException("ハンドルが無効です。")
#
#		elif status == FTDI.FT_STATUS.FT_INVALID_PARAMETER:
#			throw new USB_IOException("パラメーターが無効です。")
#
#		elif status == FTDI.FT_STATUS.FT_IO_ERROR:
#			throw new USB_IOException("I/Oエラーが発生しました。")
#
#		else:
#			throw new USB_IOException("不明なエラーが発生しました。")
#
